package it.eng.get.pdfwe.jr;

import it.eng.get.pdfwe.FOPHtmlToPdfWriter;
import it.eng.get.pdfwe.HTMLGenerator;
import it.eng.get.pdfwe.PDFWeUtils;
import it.eng.get.pdfwe.fillers.jr.JRFillerBean;
import it.eng.get.pdfwe.jr.matcher.ElementMatcher;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.export.JRXhtmlExporter;
import net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory;
import net.sf.jasperreports.engine.util.JRLoader;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;



/**
 * @author <div style="width: 100%; height: 2em;
 * background-repeat: repeat-x; 
 * background-image: url(http://www.mysoftworks.net/images/author_back.gif);" >
 * <a style="float: right; font-size: .9em; margin: 1.2em .5em .1em .1em; color: #658A16; color: 658A16; font-family: helvetica; font-weight: bold; text-decoration: none" href="http://www.mysoftworks.net">
 * Antonio Giovannetti for Engineering</a>
 * </div>
 */
// Per creare un font metric da un *.ttf
// - posizionarsi sulla cartella di installazione di Fop
// - Digitare:
// java -cp build\fop.jar;lib\avalon-framework-4.2.0.jar;lib\commons-logging-1.0.4.jar;lib\commons-io-1.3.1.jar org.apache.fop.fonts.apps.TTFReader C:\windows\fonts\arial.ttf arial.xml
public class JasperPrintWebEditor implements HTMLGenerator {
//	public static String JSP_LOCATION = "/jsp/jreport/_jasperPdfEditor.faces";
	
	public static String OUTPUT_HTML = "OUTPUT_HTML";
	public static String OUTPUT_PDFFOP = "OUTPUT_PDFFOP";
	public static String OUTPUT_JPDF = "OUTPUT_JPDF";
	public static String OUTPUT_FLYING_SAUCER = "OUTPUT_FLYING_SAUCER";
	public static String OUTPUT_ITEXT = "OUTPUT_ITEXT";

	private String ctxRoot;

	private JasperPrint print;
//	private JasperReport report;
//	private JRFillerBean jrFillerBean;
	public static final String _IMAGES_URI="/jsp/image.jsp?" + JasperImageServlet.IMAGE_NAME_REQUEST_PARAMETER + "="; // come specificato nella servlet
	public static String nonEditableClass="mceNonEditable";
	public static String editableClass="mceEditable";
	private static String editableStyle = "padding-top: 8px; line-height: 2em; padding-left: 20px; left: -20px; position: relative; border-bottom: 1px dashed black;";
//	public static String editableStyle="border-bottom: 1px dashed;";
	private String xpathEditableSelector = "/*/body//*[contains(@style,'color: #000001')]"; //  + JasperReportParser.EDITABLE_FIELD_COLOR.toString();
	private ElementMatcher elementMatcher;
	public static String htmlHeader = null;
	public static String htmlFooter = null;
	public String rawHtml = null;
	
	
	static {  
		generateHeader();
		generateFooter();
	}

	public String getCtxRoot() {
		return ctxRoot;
	}

	public void setCtxRoot(String ctxRoot) {
		this.ctxRoot = ctxRoot;
	}


	
//	public static boolean fopEnabled() {
//		return HtmlToPdfWriter.installedFop;
//	}
	
	public static void setEditableStyle(String editableStyle) {
		if (editableStyle==null) return;
		JasperPrintWebEditor.editableStyle = editableStyle;
		generateHeader(); 
	}

	private static void generateFooter() {
		htmlFooter = "\n\t</body>\n</html>";
		
	}
	
	private static void generateHeader() {
		StringBuffer _htmlHeader = new StringBuffer("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\" >\n");
		_htmlHeader.append("<html>\n\t<head>\n\t\t<title></title>");
		if (JasperPrintWebEditor.editableStyle!=null && !"".equals(JasperPrintWebEditor.editableStyle)) {
				_htmlHeader.append("\n\t\t<style type=\"text/css\">");
				_htmlHeader.append("\n\t\t\t.").append(JasperPrintWebEditor.editableClass).append(" {\n\t\t\t\t");
				_htmlHeader.append(JasperPrintWebEditor.editableStyle);
				_htmlHeader.append("\n\t\t\t}");
				_htmlHeader.append("\n\t\t</style>");
		}
		
		_htmlHeader.append("\n\t</head>");
		_htmlHeader.append("\n\t<body style=\"font-size-adjust:0.6;\">");
		htmlHeader = _htmlHeader.toString();
		
	}
	
	
	
	public static void setHtmlHeader(String htmlHeader) {
		JasperPrintWebEditor.htmlHeader = htmlHeader;
	}


	public static void setHtmlFooter(String htmlFooter) {
		JasperPrintWebEditor.htmlFooter = htmlFooter;
	}



	private Tidy tidy = null;

	public void setTidy(Tidy tidy) {
		this.tidy = tidy;
	}

	
	public void setElementMatcher(ElementMatcher elementMatcher) {
		this.elementMatcher = elementMatcher;
	}


	public boolean elementMatches(Element elem) {  // Custom matching criteria for selected org.w3c.Dom.Element, useful when xpathEditableSelector = "/*/body//*"
		if (elem==null) return false;
		if (elementMatcher==null) return true;
		else return elementMatcher.elementMatches(elem);
		
	}

	public void setXpathEditableSelector(String xpathEditableSelector) {
		this.xpathEditableSelector = xpathEditableSelector;
	}

	private void init(JasperReport report,JRFillerBean jrFiller) throws Exception {
		this.print = getJasperPrint(report,jrFiller);
	}
	

	public JasperPrintWebEditor(HttpServletRequest request,JRFillerBean jrFiller) throws Exception {
		String reportName = request.getParameter("reportName");
		JasperReport rpt = (JasperReport) JRLoader.loadObjectFromLocation(reportName);
		init(rpt,jrFiller);
	}

	
	
	public JasperPrintWebEditor(JasperReport report,JRFillerBean jrFiller) throws Exception {
		init(report,jrFiller);
	}

	
	public JasperPrintWebEditor(JasperPrint print) {
		this.print = print;
	}




	/**
	 * Gli elementi non editabili sono quelli che corrispondono all'espressione xpath 'xpathEditableSelector'
	 * e che soddisfano elementMatches()
	 * 
	 * @param doc
	 * @throws XPathExpressionException
	 */
	private void addNonEditableElements(Document doc) throws XPathExpressionException {
		XPathFactory fact=XPathFactory.newInstance();
		XPath xpath = fact.newXPath();
		XPathExpression expr = xpath.compile("/*/body/descendant::*");
		Object result = expr.evaluate(doc, XPathConstants.NODESET);
		NodeList nodes = (NodeList) result;
		// Elementi non editabili
		for (int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			if (node instanceof Element) {
				Element parent = (Element) node;
				String clazz = parent.getAttribute("class");
				if (clazz!=null && !clazz.equals("")) {
					if (clazz.indexOf(nonEditableClass)==-1) parent.setAttribute("class",parent.getAttribute("class") + " " + nonEditableClass);
				} else parent.setAttribute("class",nonEditableClass);
				
			}
		}
		
		// Elementi editabili
//		XPathExpression expr2 = xpath.compile("/*/body//*[starts-with(text(),'@editable:')]");
		XPathExpression expr2 = xpath.compile(xpathEditableSelector);
		Object result2 = expr2.evaluate(doc, XPathConstants.NODESET);
		NodeList nodes2 = (NodeList) result2;

		for (int i = 0; i < nodes2.getLength(); i++) {
			if (nodes2.item(i) instanceof Element) {
				Element node = (Element)nodes2.item(i);
				if (elementMatches(node)){

//				if (node.getTextContent() !=null /*&& node.getTextContent().startsWith("@editable:") */) {
					Node parentNode=node;
//					node.setTextContent(node.getTextContent().replaceAll("@editable:",""));
					boolean isLeaf=true;
					while (parentNode!=null) {
						if (parentNode instanceof Element) {
							Element parent = (Element)parentNode;
							
							if (parent.getAttribute("class")!=null) {
								String clazz=parent.getAttribute("class");
								if (clazz !=null ) clazz = clazz.replaceAll("\\s?"+nonEditableClass,"");
								if (isLeaf) {
									if (clazz==null || "".equals(clazz)) clazz = editableClass; else clazz = " " + editableClass;
								}
								parent.setAttribute("class",clazz);
							} else parent.setAttribute("class",editableClass);
						}
						
						parentNode=parentNode.getParentNode();
						if (isLeaf) {
//							if (parentNode instanceof Element) {
//								Element imgIcon = doc.createElement("img");
//								imgIcon.setAttribute("src", "img/modifica.gif");
//								
//							}
						}
						isLeaf=false;
						
					}
//				}
			}	
			}
		}
		
	}

	public void sendJasperPDF(HttpServletResponse response) throws Exception {
		if (response==null) throw new Exception("Response e' null");
		if (print.getName()==null) throw new Exception("Jasperprint.getName() e' null");
		response.reset();
		response.setContentType("application/pdf");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + print.getName() + ".pdf\"");
		ServletOutputStream out = response.getOutputStream();
		
		
		JRPdfExporter pdfExporter = new JRPdfExporter();
		pdfExporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
		pdfExporter.setParameter(JRExporterParameter.OUTPUT_STREAM, out);
		pdfExporter.exportReport();
		out.flush();
		out.close();
	}

	public static void sendFopPDF_new(String rawHtml,HttpServletRequest request,HttpServletResponse response,String nomeReport,ClassLoader cl) throws Exception {
		
		if (request==null) throw new Exception("Request e' null");
		if (response==null) throw new Exception("Response e' null");
		if (rawHtml==null) throw new Exception("RawHtml e' null");
		if (nomeReport==null) throw new Exception("Il nome del report e' null");
		if (cl==null) throw new Exception("Fornire lo stream di lettura al foglio xsl.");
//		HtmlToPdfWriter wr = new HtmlToPdfWriter(rawHtml,request,cl);
//		byte[] buff = wr.generatePdf(null,false);
		response.reset();
		response.setContentType("application/pdf");
		nomeReport = nomeReport.replaceAll("\\.pdf$", "");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + nomeReport + ".pdf\"");
		ServletOutputStream out = response.getOutputStream();
//		out.write(buff);
		out.flush();
		out.close();
	}

	public static void sendFopPDF(String rawHtml,HttpServletRequest request,HttpServletResponse response,String nomeReport) throws Exception {
		if (request==null) throw new Exception("Request e' null");
		if (response==null) throw new Exception("Response e' null");
		if (rawHtml==null) throw new Exception("RawHtml e' null");
		if (nomeReport==null) throw new Exception("Il nome del report e' null");
		FOPHtmlToPdfWriter wr = new FOPHtmlToPdfWriter(rawHtml);
		wr.setUseHTMLCleaner(true);
		byte[] buff = wr.generatePdf();
		response.reset();
		response.setContentType("application/pdf");
		nomeReport = nomeReport.replaceAll("\\.pdf$", "");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + nomeReport + ".pdf\"");
		ServletOutputStream out = response.getOutputStream();
		out.write(buff);
		out.flush();
		out.close();
	}


	
	private Node extractBody(Document originalDoc) {
		if (originalDoc==null) return null;
		NodeList nl = originalDoc.getElementsByTagName("body");
		if (nl!=null && nl.getLength()>0) {
			Node body = nl.item(0);
//			System.out.println("NodeValue:\n" + body.getOwnerDocument());
			return body;
		}
		return null;
	}

	
	private String extractBodyContent(Document originalDoc) throws Exception {
		Node body = extractBody(originalDoc);
		if (body==null) return null;
		return serializeDocument(body);
	}

	public String getHtml() throws Exception {
//		ReportUtils.aggiungiNumeroPagine(print);
		JasperPrintContainer.addJasperPrint(print);
		ByteArrayOutputStream os = new ByteArrayOutputStream();		
		JRXhtmlExporter htmlExporter = new JRXhtmlExporter();
		htmlExporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, ctxRoot + _IMAGES_URI);
		htmlExporter.setParameter(JRHtmlExporterParameter.HTML_HEADER, htmlHeader);
		htmlExporter.setParameter(JRHtmlExporterParameter.HTML_FOOTER, htmlFooter);
		htmlExporter.setParameter(JRHtmlExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, false);
		htmlExporter.setParameter(JRHtmlExporterParameter.IS_WRAP_BREAK_WORD, false);
		htmlExporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN,false);
		htmlExporter.setParameter(JRHtmlExporterParameter.SIZE_UNIT, "pt");
//		htmlExporter.setParameter(JRHtmlExporterParameter.ZOOM_RATIO,1.2F);
		// htmlExporter.setParameter(JRHtmlExporterParameter.IGNORE_PAGE_MARGINS, true);
		htmlExporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
		htmlExporter.setParameter(JRExporterParameter.OUTPUT_STREAM, os);
		htmlExporter.setParameter(JRExporterParameter.CHARACTER_ENCODING, "UTF-8");
		htmlExporter.exportReport();
		PDFWeUtils.tidyParse(tidy,new ByteArrayInputStream(os.toByteArray()),os);
		
		Document doc = null;
		try {
			doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(os.toByteArray()));
			addNonEditableElements(doc);
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Forse non e' stato usato il parser Tidy.",e);
		}		

		try {
			
//			String serialized = serializeDocument(extractBody(doc));
//			return extractBodyContent(doc);
			return serializeDocument(doc);
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Non posso trasformare il Document in stringa.",e);
		}

	}

	
	public static String serializeDocument(Node doc) throws Exception {
	    DOMSource domSource = new DOMSource(doc);
	    StringWriter writer = new StringWriter();
	    StreamResult res = new StreamResult(writer);
	    TransformerFactory tf = TransformerFactory.newInstance();
	    Transformer transformer = tf.newTransformer();
	    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
	    transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
	    transformer.transform(domSource, res);
		return writer.toString();
	}
	
	
	private boolean isDefaultParameter(JRParameter parameter) {
		String pname = parameter.getName();
		boolean result=
			JRParameter.REPORT_PARAMETERS_MAP.equals(pname) ||
			JRParameter.JASPER_REPORT.equals(pname) ||
			JRParameter.REPORT_CONNECTION.equals(pname) ||
			JRParameter.REPORT_MAX_COUNT.equals(pname) ||
			JRParameter.REPORT_DATA_SOURCE.equals(pname) ||
			JRParameter.REPORT_SCRIPTLET.equals(pname) ||
			JRParameter.REPORT_LOCALE.equals(pname) ||
			JRParameter.REPORT_RESOURCE_BUNDLE.equals(pname) ||
			JRParameter.REPORT_TIME_ZONE.equals(pname) ||
			JRParameter.REPORT_FORMAT_FACTORY.equals(pname) ||
			JRParameter.REPORT_CLASS_LOADER.equals(pname) ||
			JRParameter.REPORT_URL_HANDLER_FACTORY.equals(pname) ||
			JRParameter.REPORT_FILE_RESOLVER.equals(pname) ||
			JRParameter.REPORT_TEMPLATES.equals(pname) ||
			JRParameter.SORT_FIELDS.equals(pname) ||
			JRParameter.REPORT_VIRTUALIZER.equals(pname) ||
			JRParameter.IS_IGNORE_PAGINATION.equals(pname) ||
			JRXPathQueryExecuterFactory.PARAMETER_XML_DATA_DOCUMENT.equals(pname) ||
			JRXPathQueryExecuterFactory.XML_DATE_PATTERN.equals(pname) ||
			JRXPathQueryExecuterFactory.XML_NUMBER_PATTERN.equals(pname) ||
			JRXPathQueryExecuterFactory.XML_LOCALE.equals(pname) ||
			JRXPathQueryExecuterFactory.XML_TIME_ZONE.equals(pname);
			
		return result;
	}
	
	private void logReportParameters(JasperReport report,JRFillerBean jrFillerBean) {
		if (report == null) return;
		if (jrFillerBean == null) return;
		if (jrFillerBean.getParams() == null) return;
		Map<String,Object> provided = jrFillerBean.getParams();
		JRParameter[] params = report.getParameters();
		StringBuilder sb = new StringBuilder("\n\nLogging parameters:\n\t");
		for (JRParameter jrParameter : params) {
			if (isDefaultParameter(jrParameter)) continue;
			if (!provided.containsKey(jrParameter.getName())) sb.append("NOT PROVIDED:"); else sb.append("Provided:");  
			sb.append(jrParameter.getName()).append("-->");
			if (jrParameter.getNestedType()!=null) sb.append(jrParameter.getNestedType().getName()); else
				sb.append("null type");
			sb.append("\n\t");
		}
		System.out.println(sb.toString());
	}
	
	
	private JasperPrint getJasperPrint(JasperReport report,JRFillerBean jrFillerBean) throws JRException {
		logReportParameters(report,jrFillerBean);
		return JasperFillManager.fillReport(report, jrFillerBean.getParams(), jrFillerBean.getJrDataSource());
	}
	

	public String getRawHtml() {
		return rawHtml;
	}
}
